from Bases import setID
from osc_base import OSCBaseObject
from Parameter import *

class AttributeBase(OSCBaseObject):
    '''Base for all attributes.
    If initialized directly, it functions as as global attribute.
    Events and xml creation/parsing is done through this class.
    
    :Parameters:
        'name' : default is empty
        'id' : Universal ID.  Default is None (which assigns a new uuid)
        'class_name' : class specification (in progress)
        'categories' : set of categories the attribute belongs to 
        'from_xml' : xml string which, if specified, the xml will be loaded
            into the attribute.  default is None.'
    '''
    _saved_class_name = 'AttributeBase'
    _saved_attributes = ['base_class', 'id']
    _saved_child_objects = ['indexed_parameters']
    def __init__(self, **kwargs):
        self._id = None
        signals = kwargs.get('signals_to_register', [])
        signals += ['id_changed', 'new_indexed_parameter']
        kwargs.update({'signals_to_register':signals})
        super(AttributeBase, self).__init__(**kwargs)
        if 'deserialize' not in kwargs:
            if hasattr(self, 'base_class') is False:
                self.base_class = ''
            self.id = setID(kwargs.get('id'))
            self.indexed_parameters = {}
            
    @property
    def id(self):
        return self._id
    @id.setter
    def id(self, value):
        old = self.id
        self._id = value
        if old != value:
            self.emit('id_changed', obj=self, old=old, new=value)
        
    def _deserialize_child(self, d):
        for cls in [GlobalParameter, DefinitionParameter, PatchedParameter]:
            if d['saved_class_name'] == cls._saved_class_name:
                kwargs = dict(parent_device=self, deserialize=d)
                for key, val in {'GlobalAttribute':'GlobalParameter', 'DefinitionAttribute':'DefinitionParameter'}.iteritems():
                    if hasattr(self, key):
                        attrib = getattr(self, key)
                        param = attrib.indexed_parameters[d['attrs']['%s_id' % (val)]]
                        kwargs.update({val:param})
                        print 'deserialize:', kwargs
                return cls(**kwargs)
#        if d['saved_class_name'] == 'GlobalParameter':
#            kwargs = dict(parent_device=self, deserialize=d)
#            return GlobalParameter(**kwargs)
        return super(AttributeBase, self)._deserialize_child(d)

class GlobalAttribute(AttributeBase):
    _saved_class_name = 'GlobalAttribute'
    _saved_attributes = ['name']
    def __init__(self, **kwargs):
        self._name = None
        super(GlobalAttribute, self).__init__(**kwargs)
        if 'deserialize' not in kwargs:
            self.name = kwargs.get('name', '')
        if self.base_class == 'GlobalAttribute':
            self.id = self.name
        self.register_attr_watch('name')
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self, value):
        self._name = value
        if self.osc_enabled:
            self.osc_address = value
            self.osc_node.setName(value)
            
    def add_indexed_parameter(self, **kwargs):
        '''
        :Parameters;
            'name' :
            'val_min' : 
            'val_max' :
        '''
        kwargs.update({'parent_attribute':self})
        self.add_child_object(kwargs)
        param = GlobalParameter(**kwargs)
        self.indexed_parameters.update({param.id:param})
        self.emit('new_indexed_parameter', attribute=self, parameter=param)
        return param

class DefinitionAttribute(AttributeBase):
    '''Attribute that is attached to a device definition.
    
    :Parameters:
        'parent_device_id' : id of the patched device this is attached to.
            default is None
        'chan_index' : int, default is 1
        'from_xml' : xml string which, if specified, the xml will be loaded
            into the attribute.  default is None.
    '''
    _saved_class_name = 'DefinitionAttribute'
    _saved_attributes = ['parent_device_id', 'chan_index', 'GlobalAttribute_id']
    def __init__(self, **kwargs):        
        self._chan_index = None
        self.GlobalAttribute = kwargs.get('GlobalAttribute')
        super(DefinitionAttribute, self).__init__(**kwargs)
        self.register_signal('chan_index_changed', 'attr_watch_name')
        self.GlobalAttribute.connect('attr_watch_name', self.on_gl_attrib_attr_watch_signal)
        if 'deserialize' not in kwargs:
            self.parent_device_id = kwargs.get('parent_device_id')
            self.chan_index = kwargs.get('chan_index', 1)
        p_kwargs = {'parent_attribute':self}
        self.add_child_object(p_kwargs)
        for key, val in self.GlobalAttribute.indexed_parameters.iteritems():
            if key not in self.indexed_parameters:
                p_kwargs.update({'GlobalParameter':val})
                param = DefinitionParameter(**p_kwargs)
                self.indexed_parameters.update({param.id:param})
    
    def on_gl_attrib_attr_watch_signal(self, **kwargs):
        signal = kwargs.get('signal_name')
        if signal in self._emitters:
            self.emit(signal, **kwargs)
    
    @property
    def GlobalAttribute_id(self):
        return self.GlobalAttribute.id
    @GlobalAttribute_id.setter
    def GlobalAttribute_id(self, value):
        pass
        
    @property
    def name(self):
        return self.GlobalAttribute.name
        
    @property
    def chan_index(self):
        return self._chan_index
        
    @chan_index.setter
    def chan_index(self, value):
        value = int(value)
        self._chan_index = value
        if 'chan_index_changed' in self._emitters:
            self.emit('chan_index_changed', attribute=self, chan=value)

class PatchedAttribute(AttributeBase):
    '''Attribute that is attached to a patched device.
    
    :Parameters:
        'universe_id' : DMX universe this attribute is patched to.
        'chan' : DMX channel the attribute is patched to.
        'value' : int, raw DMX value.  default is 0
        'from_xml' : xml string which, if specified, the xml will be loaded
            into the attribute.  default is None.
    
    :Events:
        value_changed(attribute, value)
    '''
    
    _saved_class_name = 'PatchedAttribute'
    _saved_attributes = ['universe_id', 'chan', 'DefinitionAttribute_id']
    def __init__(self, **kwargs):
        self._value = None
        self.DefinitionAttribute = kwargs.get('DefinitionAttribute')
        self.value_set_by_osc = False
        self.users = set()
        self.captured_by = None
        super(PatchedAttribute, self).__init__(**kwargs)
        self.register_signal('value_changed')
        if 'deserialize' not in kwargs:
            self.DefinitionAttribute_id = self.DefinitionAttribute.id
            #self.patch_data = kwargs.get('patch_data')
            self.universe_id = kwargs.get('universe_id')
            self.chan = kwargs.get('chan')
            
        p_kwargs = {'parent_attribute':self}
        self.add_child_object(p_kwargs)
        for key, val in self.DefinitionAttribute.indexed_parameters.iteritems():
            if key not in self.indexed_parameters:
                p_kwargs.update({'DefinitionParameter':dparam})
                param = PatchedParameter(**p_kwargs)
                self.indexed_parameters.update({param.id:param})
            
        self.value = kwargs.get('value', 0)
        self.add_osc_handler(callbacks={'set-value':self.on_osc_set_value, 
                                        'current-value':self.on_osc_request_value})
        
    @property
    def name(self):
        return self.DefinitionAttribute.name
        
    @property
    def chan_index(self):
        return self.DefinitionAttribute.chan_index
        
    @property
    def value(self):
        return self._value
    @value.setter
    def value(self, value):
        value = int(value)
        if value != self._value:
            self._value = value
            self.on_value_change()
            if 'value_changed' in self._emitters:
                self.emit('value_changed', attribute=self, value=value)
        
    @property
    def active_parameter(self):
        result = None
        for key, val in self.indexed_parameters.iteritems():
            if val is not None:
                if val.is_active(self.value):
                    result = key
        if result != self._active_parameter:
            self._active_parameter = result
            self.emit('parameter_changed', attribute=self, name=result)
    @active_parameter.setter
    def active_parameter(self, value):
        param = self.indexed_parameters.get(value)
        if param:
            self.value = param.val_min
                
    def send_value_to_osc(self):
        if self.osc_enabled:
            self.osc_node.send_message(address='set-value', value=self.value)
            
    def on_osc_set_value(self, **kwargs):
        values = kwargs.get('values')
        if len(values):
            value = values[0]
            self.value_set_by_osc = True
            self.value = value
            
    def on_osc_request_value(self, **kwargs):
        self.send_value_to_osc()
    
    def on_value_change(self):
        if not self.value_set_by_osc:
            self.send_value_to_osc()
        self.value_set_by_osc = False
        
    def add_user(self, user):
        self.users.add(user)
    def del_user(self, user):
        self.users.discard(user)
    def update_user_value(self, user=None):
        id = self.name.lower()
        if user:
            value = user.member_values[id]
            self.captured_by = user.id
            print 'ltp=', value
        else:
            value = max([u.member_values[id] for u in self.users if id in u.member_values])
            self.captured_by = None
            print 'htp=', value
        self.value = value
